[CERC2017] Buffalo Barricades
考虑算出每个点最终被哪个矩形包含,这种情况下只有相互交叉的两个矩形可能存在覆盖关系。
此时交叉在 轴上只有一种体现,那就是比一条线靠后的射线,被这条射线在 轴上恰好覆盖。
因此按矩形的 坐标降序扫描线,用 set 维护射线的端点。因为矩形之间是编号小的覆盖在上面,所以考虑加入一条新的射线,如果被这条射线覆盖到的端点里面,有一个编号比它大,那么应当删除这个端点。
set 维护的实际上是这样一个结构:
1----.2|3--------.4|5-----------.6|
最终导致的结果,应当是端点在当前线之前的射线,编号都比当前线小。那么考虑一个点属于的矩形,就是覆盖这个点的那条射线。
考虑这样计算,只存在一种情况的矩形没办法准确计算答案,就是矩形 包含矩形 ,且矩形 比矩形 早出现。
考虑最后加入的矩形,答案一定是正确计算的。而一个没有被正确计算答案的矩形一定较早出现。
按照加入顺序倒序遍历所有矩形,那么找到在最终图形上,包含掉这个矩形的矩形,这只有一个,把答案合并到这上面即可。
xxxxxxxxxx4412const int CN = 6e5 + 10;3using namespace std;4int read(){5 int s = 0, ne = 1; char c = getchar();6 for(; c < '0' || c > '9'; c = getchar()) if(c == '-') ne = -1;7 for(; c >= '0' && c <= '9'; c = getchar()) s = (s << 1) + (s << 3) + c - '0';8 return s * ne;9}10class PAIR{11 public: int x, y;12 bool operator < (const PAIR &o) const {return x ^ o.x ? x < o.x : y < o.y;}13 bool operator == (const PAIR &o) const {return x == o.x && y == o.y;}14} ; set<PAIR> S;15PAIR mp(int a, int b) {PAIR o; o.x = a, o.y = b; return o;}16int n, q, ans[CN], fa[CN], cnt[CN], to[CN], id[CN], X[CN], Y[CN], tp[CN];17bool cmp(int i, int j) {return X[i] ^ X[j] ? X[i] > X[j] : tp[i] > tp[j];}18int fd(int x) {return fa[x] ^ x ? fa[x] = fd(fa[x]) : x;}19int main(){20 freopen("_in.in", "r", stdin);21 n = read(); for(int i = 1; i <= n; i++) X[i] = read(), Y[i] = read(), id[i] = i;22 q = read(); for(int i = n + 1; i <= n + q; i++) X[i] = read(), Y[i] = read(), tp[i] = i - n, id[i] = i;23 sort(id + 1, id + n + q + 1, cmp);24 for(int p = 1, i; i = id[p], p <= n + q; p++){25 if(tp[i]){26 PAIR cur = mp(Y[i], tp[i]); S.insert(cur);27 set<PAIR> :: iterator it = S.find(cur); it++;28 if(it != S.end()) to[tp[i]] = (*it).y;29 it--; while(it != S.begin() && (*--it).y > tp[i]) S.erase(it), it = S.find(cur);30 }31 else{32 PAIR cur = mp(Y[i], 0);33 set<PAIR> :: iterator it = S.lower_bound(cur);34 if(it != S.end()) cnt[(*it).y]++;35 }36 }37 for(int i = 1; i <= q; i++) fa[i] = i;38 for(int i = q; i; i--){39 int x = fd(i), y; ans[i] = cnt[x];40 if(to[i]) y = fd(to[i]), cnt[y] += cnt[x], fa[x] = y;41 }42 for(int i = 1; i <= q; i++) printf("%d\n", ans[i]);43 return 0;44}[HNOI2018] 排列
注意这个限制并不等价于反串的字典序最大!!能转化到字典序模型需要一个“绝对优势”。
考虑一段序列 后面接序列 ,代价是:
反过来的话,代价是:
在 前面,就等价于 。
对于确定的树形拓扑序,如果可以比较两个段哪个放在前面更好的话,一种通用的贪心方式就是每次找到最优的那个段,把它往父亲合并。
这样就有复杂度 。
扩展 KMP 复习
求 和 的每个后缀的 LCP。
定义 ,维护当前匹配出的最靠后的一段 Border ,那么如果 ,可以发现有:
直接向后暴力更新就是 。
xxxxxxxxxx101for(int i = 2, l = 0, r = 0; a[i]; i++){ // 枚举 a 的每个后缀2 if(i <= r) z[i] = min(z[i - l + 1], r - i + 1);3 while(i + z[i] <= la && a[i + z[i]] == a[z[i] + 1]) z[i]++;4 if(i + z[i] - 1 > r) l = i, r = i + z[i] - 1;5}6for(int i = 1, l = 0, r = 0; b[i]; i++){ // 枚举 b 的每个后缀7 if(i <= r) g[i] = min(z[i - l + 1], r - i + 1);8 while(i + g[i] <= lb && b[i + g[i]] == a[g[i] + 1]) g[i]++;9 if(i + g[i] - 1 > r) l = i, r = i + g[i] - 1;10}.
[CF780F] Axel and Marston in Bitland & [NOI2020] 美食家
考虑构造矩阵 ,其中 表示第 天在城市 的最大价值。
考虑转移矩阵就是邻接矩阵 ,其中 表示:
考虑一次美食节,等价于第 天的转移矩阵发生变化,记为 ,等于把 的列向量 整体加 。
max+ 矩阵乘法满足结合律,但是没有交换律,最终的转移矩阵形式是 。
这样朴素做是 ,应该能有 35pts。预处理快速幂矩阵应该可以显著减小常数,但是这不本质。
考虑直接 DP 的复杂度是 ,那么如果数据分治 ,应该有 40pts+25pts=65pts。
特殊性质 A 非常好做,这样应该就有 75pts。
如果对每个点新建五个点,就是 . 75pts.
因为最终结果只需要第一行,如果我们始终维护一个 的矩阵做乘法,那么每次乘法实际上是 的,这里复杂度瓶颈在于算快速幂时乘法是 的。
预处理 处的矩阵,那么总复杂度就是 。
[CODE FEST 2016 Grand Final] Water Distribution
对于一个块考虑,它最后整体的和一定是所有点权减掉用过的所有边权,那么最大化这个东西显然需要最小化用过的边权和,那么可以发现这个就是 MST 的边权和。
对每个可能的块预处理,复杂度 。
最后需要若干个块拼起来,这个直接就 枚举子集转移即可。
[51nod 1832] 后序与先序遍历
一棵二叉树如果某个节点只有一个儿子,那么无论这个儿子是它的左儿子或者右儿子,这棵树的先序遍历和后序遍历都是不变的。
先序遍历中 一定是 的左儿子,后序遍历中 一定是 的右儿子。除非不存在,此时 。
设只有一个儿子的节点数量为 ,那么答案就是 。
[51nod 1231] 记分牌
竞赛图的一个得分合法,等价于对所有 ,前 小的得分之和 。
[CF1303F] Number of Components
考虑如果正着做,唯一无法用并查集处理的问题是断掉两个联通块。
如果把这个过程反过来看,又相当于合并两个联通块,因此正反都做一遍即可。
保证了一个联通块至多进行一次断边或连边操作,因此是正确的。
细节???
.
高斯消元求自由元
[POJ1830] 开关问题
考虑按每个变量列方程,然后高斯消元,判断每个方程的主元是否存在即可求出自由元数量。
在实现上,由于可能无法消元成恰好的对角线矩阵,因此需要倒着枚举变量,回代方程。
.
[HAOI2018] 反色游戏
考虑有 个变量, 条方程,模 意义,求可能的解的数量。设自由元数量是 ,显然答案是 。
求解 条方程, 个变量的异或方程组。
定义一个指针 指向最后一个确定了主元的方程的位置 。顺序枚举变量 ,试图找到一个方程,并把这个方程的主元置为变量 。
如果找不到,那么显然 是自由元。否则,设这样的位置是 ,那么多了一条确定主元的方程,我们把它放在第 个位置上,然后向下消元。
这样我们可以得到一个近似的上三角矩阵,其中第 条方程的主元是其第一个满足 的变量 ,并且满足后 条方程都是无效的方程。
然后考虑回代过程,那么先枚举无效方程,此时方程右边的系数必须为 ,否则意味着方程组无解。
然后倒序枚举有主元的方程,我们总能解出该方程的主元的取值,然后向上回代即可。
这样就有 60pts。
xxxxxxxxxx101int equ(){2 int p = 1, ans = 1;3 for(int i = 1; i <= m; i++){4 if(del[i]) continue; int q = p; while(q <= n && !a[q][i]) q++;5 if(q > n) {ans = add(ans, ans); continue;} swap(a[p], a[q]);6 for(int j = p + 1; j <= n; j++) if(a[j][i]) a[j] ^= a[p]; p++;7 }8 for(int i = p; i <= n; i++) if(a[i][m + 1]) return 0;9 return ans;10}[HAOI2018] 染色
设 表示出现 次的颜色恰有 种的方案数, 表示钦点 种颜色出现 次的方案数。
答案是:
显然有:
那么:
构造 ,长度均为 。
随便 reverse 即可 .
[Gym - 102978B] Bit Operation
考虑等价于每次选相邻的两个点,然后删掉其中一个。
枚举最后保留下来的点是哪个,那么考虑前后的点必须删掉,显然这个的方案数只和序列长度相关。
考虑枚举最后一次操作删掉了哪个点,如果当前长度为 ,那么有 种方案先选择一对点然后再选出一个点,其中一种方案是不可行的,因此有 。
考虑最后前后的操作序列,只需要考虑相对顺序不变来拼接,有组合数系数 。
LGV 引理
在一张 DAG 上,有 个起点和 个终点。建立一个起点和终点之间的双射 ,问有多少路径集合 满足 ,且路径之间两两不相交。
如果这张 DAG 具备特殊性质,满足有且仅有一种双射能找到合法解,定义邻接矩阵 满足 表示从 起点到 终点的路径条数,那么解的数量是 。
.
[NOI2018] 你的名字
考虑如果询问是整串,那么我们只需要用 的 SAM 读入 ,维护一下当前读入的长度 ,那么新增的贡献是 。
考虑每次跳 均会删掉 当前读入的一段前缀,因此这样的复杂度是 的。
如果询问不是整串,那么只需要知道,某条树边是否存在。只需要维护这条边到达节点的 ,就可以知道 中是否有这个串,也就可以判断这条边是否存在。
[HAOI2018] 字串覆盖
考虑这样一个新的问题:给定串 ,求 在 中第一次出现的位置。
考虑建立广义前缀树,找到 在前缀树上对应的节点,维护 的 集合,我们只需要在线段树上二分即可。
xxxxxxxxxx7012using namespace std;345const int CN = 1e5 + 10;6int read(){7 int s = 0, ne = 1; char c = getchar();8 for(; c < '0' || c > '9'; c = getchar()) if(c == '-') ne = -1;9 for(; c >= '0' && c <= '9'; c = getchar()) s = (s << 1) + (s << 3) + c - '0';10 return s * ne;11}12int n, q, pos[CN], pb[CN], len[CN << 2], nxt[CN << 2], fa[20][CN << 2], son[CN << 2][26], idx; char S[CN], T[CN];13void ins(char ch[], bool tp){14 for(int i = 1, u = 0, c; c = ch[i] - 'a', ch[i]; i++){15 if(!son[u][c]) son[u][c] = ++idx; u = son[u][c];16 if(tp) pos[i] = u; else pb[i] = u;17 }18}19int et(int p, int c){20 int u = son[p][c]; if(len[u]) return u; len[u] = len[p] + 1, p = nxt[p];21 while(p ^ -1 && !son[p][c]) son[p][c] = u, p = nxt[p];22 if(p == -1) return u; int d = son[p][c];23 if(len[d] == len[p] + 1) return nxt[u] = d, u;24 int v = ++idx; len[v] = len[p] + 1, nxt[v] = nxt[d], nxt[d] = nxt[u] = v;25 for(int i = 0; i < 26; i++) if(len[son[d][i]]) son[v][i] = son[d][i];26 while(p ^ -1 && son[p][c] == d) son[p][c] = v, p = nxt[p];27 return u;28}29class PAIR {public: int x, y;}; queue<PAIR> Q;30PAIR mp(int a, int b) {PAIR o; o.x = a, o.y = b; return o;}31void bd(){32 for(int i = 0; i < 26; i++) if(son[0][i]) Q.push(mp(0, i));33 while(!Q.empty()){34 int u = Q.front().x, c = Q.front().y; Q.pop(), u = et(u, c);35 for(int i = 0; i < 26; i++) if(son[u][i]) Q.push(mp(u, i));36 }37}38int rt[CN << 2], ch[CN * 70][2], cnt[CN], id[CN << 2], sidx; bool d[CN * 70];39void md(int &u, int l, int r, int p){40 if(!u) u = ++sidx; if(l == r) return (void)(d[u] |= 1);41 int m = (l + r) >> 1; p <= m ? md(lc, l, m, p) : md(rc, m + 1, r, p);42 d[u] = d[lc] | d[rc];43}44int mg(int x, int y, int l, int r){45 if(!(x * y)) return x + y; int u = ++sidx, m = (l + r) >> 1;46 d[u] = d[x] | d[y]; if(l == r) return u;47 lc = mg(ch[x][0], ch[y][0], l, m), rc = mg(ch[x][1], ch[y][1], m + 1, r);48 d[u] = d[lc] | d[rc]; return u;49}50int qu(int u, int l, int r, int s, int t){51 if(!d[u]) return 0; if(l == r) return l; int m = (l + r) >> 1, tmp;52 if(s <= m) {tmp = qu(lc, l, m, s, t); if(tmp) return tmp;}53 return m < t ? qu(rc, m + 1, r, s, t) : 0;54}55int main(){56 nxt[0] = -1, scanf("%s%s", S + 1, T + 1), n = strlen(S + 1), ins(S, 1), ins(T, 0), bd();57 for(int i = 1; i <= n; i++) md(rt[pos[i]], 1, n, i);58 for(int i = 1; i <= idx; i++) cnt[len[i]]++; for(int i = n; i; i--) cnt[i] += cnt[i + 1];59 for(int i = 1; i <= idx; i++) id[cnt[len[i]]--] = i;60 for(int p = 1, i; i = id[p], p <= idx; p++) if(nxt[i]) rt[nxt[i]] = mg(rt[nxt[i]], rt[i], 1, n);61 memcpy(fa[0], nxt, sizeof(nxt)), fa[0][0] = 0;62 for(int k = 1; k < 20; k++) for(int i = 1; i <= idx; i++) fa[k][i] = fa[k - 1][fa[k - 1][i]];63 q = read(); while(q--){64 int l = read(), r = read(), s = read(), t = read(), u = pb[t];65 for(int k = 19; k + 1; k--) if(len[fa[k][u]] >= t - s + 1) u = fa[k][u];66 if(t - s > r - l) puts("0");67 else printf("%d\n", max(0, qu(rt[u], 1, n, l + t - s, r) - t + s));68 }69 return 0;70}那么只需要每次暴力寻找下一个即是 ,考虑数据分治一下瞎搞就做完了,垃圾题。
[HAOI2018] 奇怪的背包
需要注意到一个非常重要的事情:所有有效数字都应当是 的因子,而 的因子并不是很多。
首先把 变成 ,那么答案不改变,去重以后,选一个因子有 种方法。
直接对着 的因子 DP,最后暴力枚举两个因子合并答案,复杂度 。
[HAOI2018] 苹果树
给点第 次操作的点标号 ,然后考虑枚举一个点,算这个点上面那条边的贡献,就是 。
考虑枚举 ,然后算方案数。前面构造到 有 种方案,后面还剩 次操作。先选出 的子树,显然是 ,然后后面的点不能选在 的子树里,应当是 ,这部分乘起来就是:
答案是:
应该可以卷积。
[JSOI2019] 节日庆典
我们知道对于最小表示法问题,有一个 的 lyndon 分解解法:
这样做就是 的,可以拿到 30pts 的好成绩。
先考虑一个错误的贪心:取字典序最小的后缀。这个贪心的错因在于接上前缀之后,字典序可能会变大,但是这启发我们,有用的后缀是字典序最小的那些后缀。
形式化地,我们定义一个后缀 有用,当且仅当仅不存在一个后缀严格小于它。这里 严格小于 指可以找到一个位置 ,使得 且 。
可以发现,如果相邻的两个有用的后缀中,后面那个到前面那个的距离小于后面的串长,那么后面的那个没有用处。这说明了有用的后缀数量是 的。
考虑维护有用的后缀构成的集合 ,记对于前缀 ,维护出的集合是 ,那么有 。
考虑由 构造 ,我们枚举 ,那么只需要考虑它和 中最后一个元素 ,新增出来的这一位之间的大小关系,即比较两个字符的大小。这样可以确定保留 或者 。
然后再集合中新增 即可。
考虑如何计算前缀 时的答案,那么从前向后枚举每一个选出来的后缀,维护前面选出的最优解是 ,当前枚举到的解是 ,那么显然有 ,并且 是 的前缀。那么我们只需要比较剩下的部分,也就是比较 和 的大小关系即可,这个可以扩展 KMP 预处理。
因此复杂度 。
.
[CF547E] Mike and Friends
考虑类比两个串的做法,先建立广义前缀树,那么一次询问相当于是问某个子树内,线段树上一个区间里面所有出现次数的和。
乍一看这是个二维的东西,考虑有一维是没用的,能不能减少一维。
这样定义线段树: 节点的线段树区间 表示 节点的所有子串在 这个串里的出现次数。
那么依然可以线段树合并来更新,然后就没了,但是空间怎么算都不够用。
有一个利用 fail 树性质的做法:
先对所有串建立 AC 自动机,维护出每个串的接受状态。考虑将答案差分,变成询问在一个前缀内,某个串在其它串中的出现次数,那么只需要用 AC 自动机顺序读入每个串,将节点上的贡献 。那么 的询问的答案,就是 接受状态的那个子树权值的和,直接 BIT 维护即可。
时间复杂度 ,空间复杂度 。
考虑这个题的启发是:
那么:
[SRM550, R1 div1] Conversion Machine
考虑先把 最小步数变成 ,那么对于第 位,走过的步数一定是 的形式。
考虑剩下部分走的总步数,设为 ,那么关系是 ,从而可以解出 的上界 。
设剩下部分还要走 步的答案是 ,那么答案就是 ,其中有 。
考虑一个抽象的问题模型:
用 种颜色涂长为 的序列,记有 个格子涂上了第 种颜色,则需要满足 ,问有多少种不同的序列。两个序列不同,当且仅当某个位置涂上的颜色不同。
考虑对每种颜色构造 EGF,即 ,那么答案是 。
考虑单位根反演,那么 。
那么答案就是 。
可以 暴力展开 product,那么答案累加 ,其中 是多项式前面的系数乘积, 是指数上 的系数和。
注意到 ,令 扩域计算即可。
.
[NOIAC D1 A] 异或
证明:
首先有取等的条件是 trivial 的,对于 的情况,首先对高位考虑。
如果 的最高位均相同,可以规约到去掉最高位时的情况;
如果 的最高位均不相同,那么结论直接成立;
如果 的最高位相同且与 不同,那么结论直接成立;
如果 的最高位相同且与 不同,那么结论直接成立。
那么判断一个集合合法,只需要将其排序即可。
设 是满足 的最小的 ,将 去除低 位后分类,那么不同组之间显然是随便选,同一组内选择的数,必须满足第 位互不相同。
那么一个组里面只能不选,选一个或者选两个,算出每组的方案数相乘即可。
选两个的时候,相当于求有多少数,和一个数异或 ,直接 Tire 维护即可。
特殊情况是 ,特判即可。
xxxxxxxxxx13123int rt, ch[CN * 60][2], sz[CN * 60], idx;4void ins() {}5int qu(int u, int dep, LL x, LL y){ // ai ^ x <= y6 if(dep < 0) return sz[u];7 if((y >> dep) & 1){8 if((x >> dep) & 1) return qu(lc, dep - 1, x, y);9 return qu(rc, dep - 1, x, y);10 }11 if((x >> dep) & 1) return sz[lc] + qu(rc, dep - 1, x, y);12 return sz[rc] + qu(lc, dep - 1, x, y);13}.
[NOIAC D2 A] 矩阵填数
一个 的网格,每个位置需要填 的数字。给定 条限制 表示从 到 的最大值必须是 ,求有多少种方案。
考虑如果限制矩形均不相交,那么答案很容易计算。
考虑如果两个 不相等的矩形相交了,那么显然 小的那个会控制相交的区域。但是如果 相等呢?这就比较难处理。
先只考虑 互不相同的情况,那么显然将矩形排序,然后枚举一个矩形,就只需要算出在它前面的的矩形,与它的交覆盖的总面积,这部分是由这个矩形控制的。
如果只有一种 呢。考虑枚举一个集合 ,然后算 个矩形均不满足限制的方案数容斥,这个很容易计算。
考虑把这两种做法合并起来,那么考虑先算出这种 ,去除之前的矩形的并,覆盖的总面积,这就是全集,然后枚举一个集合容斥计算全集的合法方案数即可,最后答案就是所有 乘起来。
考虑难求出的东西是一个集合覆盖的总面积 ,显然 ,那么直接高维前缀和计算即可。
复杂度 。
Manacher 复习
Manacher 和扩展 KMP 算法有着异曲同工之妙,都是维护一些特殊结构,然后达到均摊 的复杂度。
Manacher 用于求出字符串某个位置的回文半径,定义为 ,其中 表示以该位置为回文中心的串的最长长度。
对于偶数串 的回文中心,我们认为它在 这个位置。
Manacher 维护一个当前找到的右边界最靠右的回文子串 ,那么可以发现 ,其中 是当前维护的回文子串。可以证明这样做的复杂度是 。
扩展 KMP 用于求出一个串 和它的每一个后缀 的 LCP 。
维护当前找到的右端点最靠右的一段 Border ,那么如果 ,可以发现 。可以证明这样做的复杂度是 。
最长回文前后缀这种东西还是回文树吧...
.
[CF573E] Bear and Bowling
爆搜的常见优化是 状压, 爆搜的常见优化是 DP,这个要记清楚了。
那么考虑一个 naive 的 DP:设 表示考虑前 位,选了 个,可以发现这个状态是容易转移的。
设 表示选 个数的最优答案,考虑加入一个 ,更新到的一定是一段后缀。直接数据结构维护就好了。
[HDU3306] Another Kind of Fibonacci
考虑 ;
。
这样就可以维护了。也就是说维护乘积求和的时候要试图找乘积的递推关系。
维护 ,转移矩阵是 。
特征方程复习
对于一阶递推 ,尝试构造等差数列:
设 满足 ,推得 ,也就是 。
那么 是等比数列,,其中 。
对于 ,用 代 ,得到方程 ,化简得到 。
设它的两个特征根是 ,那么存在系数 ,使得:
使用前两项解出系数即可。
[HDU2256] Problem of Precision
首先求 就直接做,但是现在要求 。
我们知道 。
而 是个小于 的数字。
那么 ,其中 。
那么答案就是 ,扩域即可。
树状数组上倍增
树状数组的 维护的是 这一段区间的和,如果我要问 这段区间的和,就对应到 ,前提是 是 的 lowbit,我们发现倍增过程恰好满足这个性质。
[联合省选2020 A] 冰火战士
考虑先把两个数组都升序排序,以下做法基于该结论讨论:
找到最后一个满足 的温度 ,和第一个满足 的温度 ,答案出自 中的一个。
考虑如果倍增一个温度 ,需要对 求前缀和,对 求后缀和,这不太好办。那么做差值,把 取反,温度 ,用当前的总和减掉前缀和即可。
BIT 上倍增即可 。
灵光一现
求两个字符串 内共出现了多少个本质不同的子串。
基环树的 Prufer 序列构造
每次找到编号最小的一个叶子,把它的父亲加入序列。如果它的父亲在环上,那么不删除;否则删除这个父亲。
那么可以发现,树上点 在序列中出现次数是 ,环上点 在序列中出现次数是 ,并且序列中最后一个点一定是环上的点,并且序列的长度 = 点数 - 环长。
如果这个时候已经确定了环的形态,那么就可以确定这个基环树。这里的形态指的是有哪些点,以及点的顺序。
注意一个大环没办法用这种构造表出,或者说表出的结果是空序列 + 大环。
[Code FEST 2017 R3 J] Unicyclic Graph Counting
给定 个点各自的度数 ,求有多少个满足该度数序列的有标号基环树。
直接 DP 对推广的 Prufer 序列计数,维护 ,即可 。
注意 个点的有标号环有 种形态。
毒瘤
个点的有标号树有 种形态,那么 个点的有标号基环树有多少种形态?
树是简单图,因此不能有自环或者重边。
先枚举一个环长,构造上面的序列,有:
.
同余最短路
你一开始在 处,每次可以走 步,求你在 中能到达多少不同的点。 很大,但是 很小。
设 ,那么每个 都可以被表示成 的形式,其中 。
设 表示满足 的,最小能到达的点 。那么形如 的点都可以到达,如果能对每个 求出 ,我们就能解出问题的答案。
考虑对每个 和 ,连边 ,那么 等价于在这张点数 ,边数 的图上从 到 的最短路,使用 Dijkstra 求解即可。
[WC2016] 论战捆竹竿
显然等价于一个模型:一开始在 ,每次可以走 步,问 中可以到达多少点。这里 。
显然 是 级别的,那么直接建图是 的,这样有 50pts。
不会了/kk
reflect
线段树合并的时候,判断要这样写:
xxxxxxxxxx11if(!u || !v) return u | v;不要用乘积!!乘积会溢出,很可能被对着卡!!
[THUSCH2017] 大魔法师
先把信息写成向量的形式,然后通过矩阵来进行变换。
考虑 1 操作,相当于乘上 ;
考虑 2 操作,相当于乘上 ;
考虑 3 操作,相当于乘上 ;
考虑 4 操作,相当于加上 ;
考虑 5 操作,相当于乘上 ;
考虑 6 操作,相当于先乘 再加 。
矩阵的运算满足结合律和分配律,因此维护乘法和加法标记即可。
具体来讲,维护当前区间的值是 ,区间整体加的时候直接加在外面,区间整体乘的时候两边都要乘。注意永远都是右乘。
循环展开,然后做乘法 的时候特判掉 的情况,也就直接 continue,常数会小很多。卡到了 loj 第三页,我不行。
当构造函数被调用很多次而可以特判取代时,不写构造也能减少常数。
.
[ZJOI2013] K大数查询
二分一个 ,将询问划分成两类:答案 和答案 进行分治。
考虑分类的过程, 我们只需要把 的修改,在 区间加一,那么对于一个询问,它的答案 当且仅当 。
注意线段树需要还原,复杂度 。
[AGC001 E] BBQ Hard
考虑即是求 ,显然它等于:
考虑求前面一个柿子, 等价于网格图上,从 走到 的方案数,每步只能向上或者向右走。
考虑对整体递推,就是把每个 处的 DP 值设为 ,然后向右和向上递推,在每个 处统计答案。
,其中 是值域。
Border 的四种求法
Periodicity Lemma 指出了周期之间是存在联系的,这个联系基于一个基本事实:周期的倍数依然是周期。
存在结论:
当 的时候,这个结论很好理解,因为这个时候串可以被表示成 的形式,其中 是 Border, 可空。那么周期显然是 。
当 的时候,这个时候我们把串理解成 的形态,其中 是 Border 并且满足 是 的前缀。显然有 ,那么 这一整个部分一定在后面的 中出现了,然后我们可以把 去掉,把 分解成 。由此不难发现 是串的周期。
根据上面的推导,不难发现反过来也是成立的:
也就是说一个串的 Border 和一个串的周期是本质相同的集合,在表现上形成互补的关系。
[HDU3746] Cyclic Necklace
求整串的最短周期,相当于求串的最长 Border,直接 KMP 即可。
[WC2019 Lecture] 周期串查询
求 是否可以被分解成 的形式,其中 , 是 的可空前缀。不存在输出 。
即求最小周期,是下面这道题的弱化版。直接求出所有 runs,那么答案相当于一个矩形前缀求最小,直接离线二维数点即可。
[BJWC2018] Border 的四种求法
.
[Gym102978H] Harsh Comments
有 种 A 物品,权值 ; 种 B 物品,权值 。每次操作可以随机选择一个物品删除,概率是 或 ,其中 是当前状态下剩余所有物品权值的和。求删光所有 A 物品的时间的期望。
考虑 min-max 容斥,那么就枚举一个子集 ,求这个子集的元素至少有一个被删掉的时间的期望。
可以把这个子集看成一个整体,设权值和是 ,那么期望是 。
方便起见权值记 ,那么答案是 。
这里 表示这种状态的系数和。有多少种方案能选出一个 A 的子集,使得不包含 且和是 ,求它们的系数和。
对 A 的前后缀做两遍背包,。枚举 ,通过前后缀拼出方案数,。
考虑先做出背包 ,。然后消除 的影响,就是除掉 ,直接模拟即可,。
总复杂度 ,其中 是 的值域。
矩阵转置与转置原理
[bzoj3583] 杰杰的女性朋友
一张 个点的图,每个点有一个 维入度向量 和 维出度向量 。
对于任意两个点 , 有 种走法。
次询问 ,表示从 到 经过不超过 条边的路径有多少条。
可以发现转移矩阵就是 ,我们要求 。但是 是 的矩阵,这样显然不行。
长度恰好为 时的答案就是 ,中间那一部分就可以快速幂了。
答案就是 ,设 ,考虑中间那一部分怎么计算。
处理 ,考虑倍增预处理 ,有 。
然后对 的每个二进制位分段,然后每一段就是一个 乘上一个 的形式。
.
训练 31 订正
B.
考虑直接点分治,然后维护前缀李超树,时间复杂度 ,空间复杂度 ,常数大的一匹,没过去。
考虑如果是序列问题,就直接 分块,一个询问被拆成至多 个整块询问,由于李超树查询是一个 ,所以复杂度 。
考虑先把询问按照重链剖分的 dfs 序划分成 段,那么现在有至多 个询问,搞成 个整段查询,然后复杂度 ,看起来都不如直接点分治,但是感觉会松一些。
考虑把 个区间对应在线段树,然后对线段树的每个节点建李超树,这部分复杂度 ,然后 次询问查询的复杂度就是 ,常数比点分治小很多。空间 。
李超树两个 注意别写满,第二个 很松,没用的线段直接 return 即可。
A.
考虑即相当于有一些点,一些只能往左走,一些只能往右走, 一些没有限制,求 的方案数。
考虑一个非状压的 DP,设 表示考虑前 个点的状态,注意到只考虑前 个点路径可能未结束,也就这里面有很多段路径。再加一位同时维护路径的段数,设 表示考虑前 个点,现在有 段路径未结束。因为路径没有结束,所以一段路径的末端一定是 R 点或者 B 点。
考虑当前 ,如果是 L,那么可以接在一段路径后面,也可以把当前路径连在起始段后面。如果是 R,那么可以新建一条路径,也可以接在一条路径前面。直接转移即可,复杂度 。
.
C.
考虑用 LCT 维护森林,然后两个子树合并的时候,现在的并集应该等于两个部分的大小加起来,然后减掉交集。
考虑这个交集,实际上就是上一次合并时这两两个连通块的并集,因为显然新加入的元素不可能有交。
于是直接拿 LCT 维护每棵树的答案,合并分裂都解决了,每次分裂的时候,在这条边上记录一下现在的并集大小即可。
复杂度
.
[雅礼集训 2017 Day1] 字符串
考虑直接每次询问单独做,然后对 建立广义 SAM,然后询问 可以直接枚举 然后在前缀树上倍增找到对应点,直接累加答案,复杂度 但是很松,估计可以过 50pts。
注意到 ,然后数据分治...
B. Replicate Replicate Rfplicbte
[CF 1276F] Asterisk Substrings
把需要统计的串划分成六类 。然后显然 都是 1,并且这两类不可能算重。 可以简单求出,并且不可能算重。剩下的只有 三种情况,并且这三种情况里是可能算重的。
考虑所有前缀,最后一个字符替换成 ,能有多少本质不同的子串。由于最后一个字符相同,所以可以删掉,那么实际上就是所有小于串长的前缀有多少本质不同的子串。所有后缀也同理。这样算这一部分内部不会算重,也不会和 的情况重复,因此只剩下 这种情况。
考虑对于 endpos 相同的 ,可以选择的 是均相同的,也就是需要计算一些后缀组成的集合的本质不同前缀数量。后者显然等于这些后缀的长度和减掉排序后相邻串的 LCP。
考虑 set 启发式合并,就是 ,这样需要同时建立前缀树和后缀数组结构。
一些后缀的本质不同前缀数量,等于这些后缀在后缀树上对应节点到根的链长并。实际上可以直接求 dfs 序,然后按照这个启发式合并。
梳理一下做法,先同时建立前缀树和后缀树,然后先算前面五个部分的答案。搞出后缀树的 dfs 序和欧拉序,然后处理出 LCA。定义后缀结构体,维护这个结构体的 set,然后按 dfs 序倒序遍历前缀树,维护 endpos 整体 +2 的集合和它的权(set),然后启发式合并,每次合并相当于取消掉原来两个的贡献,加入新加入的两部分贡献。
最小回文划分
只需要考虑对最长回文后缀应用 WPL 即可。
设 ,设一段等差数列的末端是最长的一个 满足 ,记一段等差数列的 均指向上一段等差数列的末端。
考虑设 表示 所在的等差数列中,比 段的回文串对应的转移位置的 值的并集。当一个回文后缀再次出现时, 需要被重新更新,这时它的准确值是 加上新加入的一个元素,是 。
只需要用 去更新 即可。复杂度 。
xxxxxxxxxx161void clr() {nxt[0] = len[0] = -1, lst = idx = 1;}2int p = lst, c = ch[i] - 'a';3while(ch[i - len[p] - 1] ^ ch[i]) p = nxt[p];4if(!son[p][c]){5 son[p][c] = ++idx, len[idx] = len[p] + 2;6 int pp = nxt[p]; while(pp ^ -1 && ch[i - len[pp] - 1] ^ ch[i]) pp = nxt[pp];7 nxt[idx] = pp ^ -1 ? son[pp][c] : 1;8 di[idx] = len[idx] - len[nxt[idx]];9 lk[idx] = di[idx] ^ di[nxt[idx]] ? nxt[idx] : lk[nxt[idx]];10}11lst = son[p][c], n++, f[i] = 2e9;12for(int j = lst; j > 1; j = lk[j]){13 g[j] = f[i - len[lk[j]] - di[j]];14 if(di[j] == di[nxt[j]]) g[j] = min(g[j], g[nxt[j]]);15 f[i] = min(f[i], g[j] + 1);16}注意这里 一定会在 之前被更新掉,因为它是 的 Border!!
[CF 932G] Palindrome Partition
如果 ,那么把 穿插到 中间得到串 ,串 必然是回文串。例如:
xxxxxxxxxx31A : abcd2B : abcd3C : ad bc cb da
考虑 穿插 , 穿插 ......可以发现这等价于拿 穿插 ,然后就会得到一个新的串 ,每对 在 中双射一个偶回文子串。
那么相当于求 的偶回文划分数量,这个只需要钦点每个状态都只出现偶回文串即可。
[CF 906E] Reverses
考虑一个 naive 的 DP,设 表示考虑前 个位置的答案,那么显然有 且 。显然 应该满足 。
由于 DP 数组有单调性(错的!),所以求最小的 。把 反过来,相当于从 向前从 向后求一个反向 Border,显然它的本质和区间 Border 相同,然而区间 Border 是众所周知的不可做问题。
考虑两个对应位置的子串反转其中一个会相等,就等价于两个子串插起来之后得到的是一个偶回文串。
求最小偶回文划分即可。
.
[CF590E] Birthday
一眼秒了。
先建出 fail 树,显然一个串的子串是它在 fail 树上的祖先。我们希望不存在偏序关系,等价于求 fail 树的最长反链,也就是传递闭包后的最小链覆盖。
就左边建一排点,右边建一排点,每个点向源 / 汇的流量都是 1,然后右点向左点连 边,然后答案是点数 - 最大流。
求方案?
考虑求出拆点二分图的最大独立集,具体操作是:
从每个没有匹配的左部点开始 dfs,左->右的边只走没有匹配的边,右->左的边只走匹配的边,然后所有 dfs 到的左部点和未 dfs 到的右部点就组成了最大独立集,左右都在最大独立集里的点 就在最长反链里。
[CF700E] Cool Slogans
前缀树上的最长链?有一个问题是,转移时应不应当加一。
如果父节点在子节点中出现了两次以上的话,就应该加一。否则可以把父节点替换成这个点,然后不加一。
线段树合并即可。
坑点:转移的时候需要维护出现在这个节点实际上是哪个点,因为可能不选择这个节点。
坑点:必须从树根往下 DP,因为倒过来的过程可能有多种不同的选择,但是从上向下只有一种选择。
[HDU5390] Tree
可持久化 Tire 的带修只能类似于线性基带修那样,线段树分治来解决。
一个序列带修可持久化 Tire 的在线想法:按序列位置线段树分治,然后一个元素持续存在的位置是一段前缀,它会被拆分成 个可行段。于是我们总共有了 个元素,我们对线段树的每个节点建立 Trie,这样时空复杂度都是 。
或者换句话说,对集合(强调无序性)建立的可持久化结构,有一个通用的带修方法。因为本质上这类问题相当于“给一些元素,每个元素在 这一段位置内有效,维护每个区间对应集合的 DS”。
这个问题有一个通用解决办法就是,把一个元素在线段树上划分成 段区间,然后由于查询是单点查询,所以标记永久化,在线段树的每个节点上,维护对应集合的 DS。
这样做的空间复杂度是 ,时间复杂度是 ,其中 是该 DS 一次操作的空间复杂度, 是该 DS 一次操作的时间复杂度。
通常理解的线段树分治相当于对时间轴可持久化,本质上还是解决上面的模型。
实际上是两种角度!!一种角度是用 BIT 那样的单层结构实现可持久化结构,另一种角度是用线段树或分块那样的多层结构实现可持久化结构。
本质上分块是三层线段树!!
考虑原问题,首先想到可以树上可持久化 Tire,但是没有树上 BIT 所以无法支持修改,这样不行。
因为所有查询路径都是从根出发的,这种情况下用不着树链剖分!!树链剖分解决的是任意两个特殊点之间,路径查询的问题。如果一个端点固定,可以被 dfs 序完美代替,因为这个时候另一个点的影响区间在 dfs 序上一定是连续的!!
考虑拿多层 DS 维护。一个元素的影响范畴是它的子树。对 dfs 序建立线段树,然后线段树上每个节点维护一个 Tire,就可以支持在线修改和查询,时空复杂度 ,空间上过不去。
考虑可以对线段树上每个节点维护一个 set,里面存所有操作中覆盖到这个区间的元素,按出现时间排序。然后可以只维护一棵 Trie,对每个节点单独做一遍 RMQ,时间 ,空间 。
回文树的双端插入和后端删除
双端插入:同时维护两个指针 ,分别指向当前串的最长回文前缀和最长回文后缀。注意到一个串的最长回文前缀同时也是它的最长回文后缀,因此从 开始跳 即可。
[HDU5421] Victor and String
从前后段插入字符,询问本质不同的回文串数量和所有回文串数量。
本质不同回文串的数量是回文树的节点数。所有回文串的数量是 集合大小之和,这个考虑贡献,每新建一个字符直接求祖孙链和即可。
xxxxxxxxxx151int nxt[CN], len[CN], son[CN][26], lstl, lstr, pl, pr, idx;2void clr(){3 for(int i = 0; i <= idx; i++) memset(son[i], 0, sizeof(son[i])), nxt[i] = len[i] = 0;4 nxt[0] = len[0] = -1, lstl = lstr = idx = 1;5}6void ins(int &lst, int c, int i, int tp){7 int p = lst; while(ch[i + tp * (len[p] + 1)] ^ ch[i]) p = nxt[p]; 8 if(!son[p][c]){9 son[p][c] = ++idx, len[idx] = len[p] + 2; int pp = nxt[p];10 while(pp ^ -1 && ch[i + tp * (len[pp] + 1)] ^ ch[i]) pp = nxt[pp];11 nxt[idx] = pp ^ -1 ? son[pp][c] : 1, ed[idx] = ed[nxt[idx]] + 1;12 } 13 lst = son[p][c], ans += ed[son[p][c]]; 14 if(len[lst] == pr - pl + 1) lstl = lstr = lst; // border case!15}一种不基于势能分析的插入算法:维护一个转移数组 表示在 点后面扩展一个字符 ,转移到的那个点 ,其中 代表的串是最长的 的回文后缀,满足 的前驱字符是 。
考虑更新 数组,显然 是在 的基础上进行了 级别的更新。设 的前驱字符是 ,唯一的更新是 。
注意如果 的前驱恰好是 ,那么实际上要找的点就是 。
这样时空复杂度都是 ,可以通过可持久化数据结构优化到 。
在 Trie 上建立回文树 / 末端删除:可以发现后者完全是前者的子问题,而前者可以通过上面的算法简单实现。
当然了 指针也必须可持久化才行。
类似的处理办法还出现在了 KMP 自动机中。本质上这的确是一类自动机,只不过没有起始状态和接受状态。
[HDU5394] Trie in Tina Town
就是上面的模板。
xxxxxxxxxx221/* 2to[u][c] 表示如果 u 后面新增一个字符 c, 跳 nxt 的执行结果 3在递归子节点之前把子节点的 to[] 更新成正确的值即可4*/5int len[CN], nxt[CN], lst[CN], son[CN][26], to[CN][26], idx;6void dfs(int u, int dep){7 for(int k = hd[u]; k; k = E[k].nxt){8 int v = E[k].to, c = E[k].c, p = lst[u]; ch[dep] = c;9 if(ch[dep - len[p] - 1] ^ ch[dep]) p = to[p][c];10 if(!son[p][c]){11 son[p][c] = ++idx, len[idx] = len[p] + 2; int pp = nxt[p];12 if(pp == -1) nxt[idx] = 1; 13 else{14 if(ch[dep - len[pp] - 1] ^ ch[dep]) pp = to[pp][c];15 nxt[idx] = son[pp][c];16 }17 memcpy(to[idx], to[nxt[idx]], sizeof(to[nxt[idx]]));18 if(dep > len[nxt[idx]]) to[idx][ch[dep - len[nxt[idx]]]] = nxt[idx];19 }20 lst[v] = son[p][c], dfs(v, dep + 1);21 }22}.
[LOJ517] 计算几何瞎暴力
考虑一次排序之后,下一次排序之前,这个数组每时每刻都可以被划分成两个部分:前面一部分是有一定顺序的,后面一部分则按照加入顺序排列。
考虑这一段之间内,前面那一部分,一定是先是升序的,然后某次 4 操作之后,变成了无序的。但是不考虑这次 4 操作,依然是升序的。如果我们给 4 操作打一个 lazy tag,那么这部分就是升序的。
考虑分开维护,前面一部分拿一个 Trie 维护,询问相当于求前 大之和;后面一部分拆开位拿数组维护,询问相当于求前缀和。
1 操作,直接接在后面那部分。
2 操作,前后分开询问。后面的前缀和是正确的,前面的需要对 Trie 上每个节点维护 个信息,表示这个子树每一位上有多少 1。
3 操作,后面的前缀和拆位更新,然后整体打一个异或 tag ,表示现在每个数是 。
4 操作,把 Trie 树的整体 tag ,然后把数组里面的数字都加入 Trie。
复杂度 。
[CF963D] Frequency of String
考虑长度不同的串只有 种,对于同一种长度,只可能有 种 。
于是就 set 启发式合并然后直接做了。
[CF587F] Duff is Mad
先建 fail 树。考虑一个串的所有出现位置是它在 fail 树中的子树。
把询问差分:问 在 中出现了多少次。
考虑从 到 枚举 ,然后给 的所有出现位置 ,就相当于子树 。然后查询一个 ,就相当于对 AC 自动机上的一条路径求和。
考虑按串长分块,考虑串长 的串直接暴力求出这条路径,串长 的串只有 个,对 fail 树上每个点额外维护一个 表示这个子树里面,第 种长串有多少个。长串的答案我们直接单独维护,每次子树加,枚举长串直接答案加权即可。
[NEERC2015] Distance on Triangulation
考虑对于三角剖分的一条边,它左边部分的点集是 ,右边部分的点集是 ,两个端点是 ,则所有 之间的路径一定且恰好经过 中的一个。
然后我们就可以找一条边使得左右两边分的尽量均匀,然后从这两个点开始广搜,然后分治下去计算。
形式化的理解,相当于在维诺图(对偶图?)上边分治,因为这里维诺图一定是一棵树,可以简单证明。
考虑怎样建立维诺图,就只需要找度数为 的点,然后把这个点删掉,然后给这个区域新建一个点,然后把新点挂在对边上。
每删掉一条边的时候,把当前的点个边上的点连起来。
slstxdy
给定一个点 和二维平面上的若干点构成的集合 ,求有多少种方案可以选出一个 ,使得 中任意三个元素构成的三角形不包含 。
数据保证任意三个点不共线。
不会做/kk
.
组合数系:任意一个正整数 可以被表示成 。
[LOJ6055] 一道数学题
首先解递推式,得到通项是 。后面是一个比较畸形的自然数幂和问题,应该不是一个关于 的 次多项式。
错位相减: 尝试求出 和 的关系。好思路!!
有时候错位相减会减不出东西来,但是碰上没有求和方法的柿子要往这方面想!!
如果求和式中包含组合数,那么错位相减大概率可以成功。如果求和式中包含指标的定次幂,那么应该先展开一下搞出组合数。
这样所有的 可以在 时间内推出,只需要 处理出第二类斯特林数,就有 的 60pts.
考虑一行第二类斯特林数可以直接拿通项公式卷积,这样就是大常数 ,可以通过原题非加强版。
关于错位相减的总结:
,构造 ,然后直接卷积即可。
朴素做应该可以 ,好像可以通过插值做到 。
:常幂展开之后,需要对所有 求出 ,这个柿子真的能做吗.../kk
[LOJ6608] 无意识的石子堆
的棋盘,有 个棋子,求有多少种放棋子的方案,使得每个行每列都只有不超过两颗棋子。
考虑先确定列的分配,再确定行的分配。枚举 列放了两个,那么有 列放了一个。
答案是 ,其中 表示已知 列放了两个的情况下,有多少种合法的行分配方式。
:有 个小球,共 种颜色,有 个不同的盒子,其中 个盒子需要放两个小球, 个盒子需要放一个小球。同一个盒子里面的小球必须异色,求放置方案数。
首先只需要强制所有盒子都不为空,然后插板,就能满足接近所有限制。唯一不满足的限制是一个盒子里面的小球必须异色。
根据二项式反演,恰没有盒子使得小球同色的方案数等于钦点 个盒子里的小球同色,然后容斥,系数都是 。
那么这里就是 。
卷积即可,这样就是 ,只需要一次卷积,不过常数还是不小。
[LOJ6289] 花朵
考虑直接有多大力就多大力 DP,复杂度 ,可以拿到 37pts.
考虑如果是一个菊花,只有两种可能:选根,其它的全不选;不选根,其它的随便选。然后分治 FFT 一下,总共 52pts。
考虑如果没有摘掉的节点不相邻这个限制的话,相当于一个广义背包,可以直接分治 FFT 解决掉。
相邻不选的 DP 方程也可以写成卷积!这样来做,设 表示不选 对应的多项式, 表示选 对应的多项式,那么 ,同时有 。
答案就是 。
那么一条链也做完了,这样有 67pts.
生成函数的乘法是可以交换的,考虑链分治,把树剖成若干链,然后按照 dfs 序的倒序处理每一条链。先遍历一遍,把链上每个点在这个时候真实的多项式求出来,然后对整条链带权分治 FFT。
考虑这样做什么时候会有优化作用:每次处理一条链的时候,每个节点的多项式大小尽量均匀。容易发现多项式大小和子树大小是相关的,因此选用重链剖分,这样会使得每个点在乘到根的过程中,只出现在 个多项式中。结合一次分治的复杂度,总复杂度 。
[AGC005F] Many Easy Probs
考虑 个点的最小联通子树的大小和,可以算每个点的贡献。考虑一种选取方案里面,一个点有 的贡献,当且仅当有两个点之间的路径会经过这个点。
考虑所有点对路径都不经过这个点,等价于只能在挖掉这个点之后的若干联通块里面选择。于是贡献和是 ,这里上方子树我们也认为是一个子树。
考虑对 分类,得到:
这里直接把 减 :
这样就直接卷积。
.
[LG P5824] 十二重计数法
个球放到 个盒子里。
[CF1096G] Lucky Tickets
首先题目就是让我们求的就是 项式系数第 行的平方和,即对所有满足 ,,长度为 的序列 ,求 的和。
构造 ,答案即是 ,随便怎么搞搞就行,复杂度 或者 。
好像不是很能这样转化....因为有时候,两个系数带权乘起来加起来可能是相等的。或者换句话说,有 个系数非零项的多项式的 次幂不一定恰好有 项。
那就按照朴素的思路来,构造 ,那么答案是 的各项系数的平方和。
求幂:设 ,两边对 求导,其中 。
也就是 ,也就是 。
比较系数得 ,把 那一项连同系数提出来就可以 递推出每一项系数了。
有效的 一共有 项,而 的有效项只有 个,因此复杂度 。
[PKUWC2018] 猎人杀
对于一个整体,它出现在 1 之前或之后的概率与其它元素是毫无关系的,只跟这个整体的权值和和 1 的权值有关。
考虑算“某个集合里的所有元素均在 1 之前出现”的概率并不好算,因为分母每时每刻都是在变的,这时候我们不能把这些元素当做一个整体。
但是反过来,算某个整体均出现在 1 之后的概率十分好算,这个时候答案实际上就是 ,其中 表示这个整体的权值和。
于是我们考虑容斥,钦点一个点集 个的人在 之后被选到,答案应当是:
这样可以像二项式反演那样按照集合大小分类,也就是设 表示选 个数和是 的方案数,答案是 ,背包一下就是 ,有 50pts。
然后想到可以直接按照集合的和来分类,设 表示和为 的所有集合的容斥系数和,答案是 。
剩下的问题是处理 ,这里每次往 里面添加一个元素,我们都要把系数乘上 ,因此有 。
这个可以按照区间内 的和带权分治,复杂度 。
枚举子集容斥的容斥系数和是可以背包,并且可以写出生成函数的!!这一点一定要记清楚。
[XXI Opencup GP of Tokyo H] Harsh Comments
有 个 A 物品,权值 ; 个 B 物品,权值 。每次你会以 的概率选择一个 A / B 物品把它扔掉,其中 是剩余所有物品的权值和。求你把所有 A 物品扔光的期望操作次数。
考虑 min-max 容斥,相当于对所有 A 物品的集合 ,求集合 中有任意一个物品被删掉的期望时间,容斥系数是 。
这个时候物品分两类,一类在集合 内,一类在集合 外。 内的元素可以看成整体,实际上可以考虑贡献,答案就是 。
答案是 。
首先 不大,可以按照 分类。如果先枚举 ,柿子变成 。
处理出不包含 的背包 ,那么实际上一个 的求和就是 ,这里的 是所有和为 的集合的容斥系数和。
这个可以背包,考虑新加入一个元素,所有集合的系数同时乘 ,也就是 。
相当于乘上一个生成函数 ,因此是可逆的,每次枚举 的时候模拟除法即可。
复杂度 。
.
[CF755G] PolandBall and Many Other Balls
白给好题
有 个球,可以选连续的两个作为一组,也可以选一个作为一组,求对每个 ,分 组的方案数。
考虑先选出连续两个的放置方案,相当于求 个位置,放 可互不相邻元素的方案数。这个可以容斥然后卷积计算就是 。
然后再选出连续一个的放置方案,发现没法卷积...
考虑对于 ,答案实际上很好确定。枚举 个位置放了两个,然后先保留最后 个位置不选择,然后从前面的 个位置选出 个,然后再从 个位置里面选出 个把它变成两个,后面选择的位置顺次向后推移,可以发现这和原问题等价。
发现还是没法卷积...
考虑 的组合意义还有一种解释:先从前 个里面选任意个,然再从剩下的里面选 个,需要不会选重。
可以钦点前面有 个选重了,得到另一种表示:
很大的时候阶乘没法表示,不需要分段打表!因为 很小,除掉一个 就可以做了,记住这个技巧!!
处理下降幂即可,一次卷积 。
D5 A
定义积性函数 ,求 的前缀和。
考虑贡献法,实际上答案是 ,然后就是莫比乌斯反演那一套...
.
D6 B
如果行列式的某行或者某列有公因子,可以把它提出来计算。
矩阵和它的转置的行列式相等。
考虑答案即是 。
提公因子得:
可以发现这里每行都是关于 的 次多项式,且每行形式相同,因此可以通过变换把每行变成 的形式。
提 因子,发现这个时候是一个每行的公比是 的范德蒙德行列式,范德蒙的行列式有引理 ,注意这里一定是大下标减掉小下标。
考虑按照 的值分类,就对 和 分别做桶然后卷积。注意这样一对 会被算两遍,一遍正的一遍负的。因为我们知道结果总是正的,所以枚举一半即可。
D6 C
先考虑怎样选出一个 LIS 等于序列最大值 的序列。
记住上面两种方法!!选最前面的那个作为代表点,剩下的部分的方案数实际上可以表示出来!!
考虑枚举 的位置,设 表示值域 ,长度为 ,LIS 长度为 的序列有多少个,那么:
可以发现这是一个对 的卷积,当然了这对原问题并没有帮助。
显然后面 这一部分每个数字出现次数都是相等的,那么前面这一部分满足吗?
考虑所有情况下,每个数字不能出现的段长的和一定是相等的!这可以通过交换位置问题不变来理解。所以前面部分每个数字出现次数也是相等的。
换句话说,当 确定的时候,每个数字的出现次数相等且都是 。
这样朴素做就是 。但是 依然不能做。不会/kk
.
[CF643G] Choosing Ads
区间严格众数有个简单做法:线段树每个节点维护一个二元组 表示当前区间的严格众数是 ,出现次数是 。合并两个区间的时候,如果 ,那么 ,否则 。
设 ,维护 个二元组。合并的时候,先保留全部元素,按照出现次数排序,然后可以断言超出 的那些元素没用,维护差值即可。
注意区间众数和区间带修众数是一个莫队问题。
[ARC093D] Dark Horse
首先 放在哪里答案都是一样的,证明可以考虑 rotate,因此设 ,然后答案需要乘 。
考虑 个集合 ,实际上需要每个集合的最小值都不在 中。
枚举一个 ,容斥计算钦点 中的每个 的最小值都在 中的方案数。
考虑能和 一起分到某个段的元素,是大于 的元素。如果把 倒过来,然后枚举每个 尝试把它分配到一个 ,这个时候已经选过的 的数字个数只跟现在状态下 选取情况有关。因此可以倒着做状压 DP。
这样就是 。
[CF708E] Stu Camp
考虑一个状态数是 的 DP:设 表示考虑前 行,第 行的状态是 的概率。直接转移就是 的。
显然可以优化,设 。处理这两个数组一共需要 的时间,然后转移就是 。
设 ,这两个直接线性预处理,那么:
维护 即可。
复杂度 。
[雅礼集训 2018 Day4] Magic
考虑一个 naive 的 DP,设 表示考虑了前 个位置,有 对相邻位置,最后一个的颜色是 。朴素转移是 的,稍微冷静一下就会发现本质上实际上 的,然后 10 个点到手。
考虑每种颜色的卡的使用数量已经确定,那么给卡标号,这样每种颜色的卡可区分,最后答案除 。
设 表示钦点 对相邻位置的方案数,然后二项式反演,答案是 。
考虑 个位置中有 对相邻,那么这个序列永远可以被划分成 个排列。考虑将每种颜色 ,划分成 个排列的方案数组合起来,就可以得到将长度为 的序列划分成 个同色排列段的方案数。
设 表示将颜色 划分成 个排列有多少种方案,。设 是 的 OGF,那么 。
对 带权分治 FFT 即可,复杂度 。
.
[CF1349F2] Slime and Sequences
草,某题居然是原题
考虑枚举序列最大值 ,用最靠前的 LIS 双射序列,那么这个时候序列的数量就是 。
考虑对于确定的 ,所有 的数字出现次数一定是相同的,因为后面 这部分显然相同,前面部分一个数字在所有情况下能出现的段长和相同。
因此这个时候贡献是 ,然后 ,Easy Version 解决。
不一样!!比如 2 3 1 2 这样也是合法的。
考虑容斥,设 表示钦点 个数字不合法的方案数,那么答案是 。 怎么数啊....
考虑一个好序列 1 2 1 3 4 5 4,可以顺序写下每个数字出现的位置 (1, 3) (2) (4) (5,7) (6),我们发现题目限制等价于第 个括号的最大值大于第 个括号的最小值。把每个括号降序排列,可以发现每个括号实际上是一个排列的极长下降子段。显然这是双射。
这样我们建立了从排列到好序列的双射,由此也不难知道好序列的总数一定是 。
一个数字 的出现次数,就等于所有 个排列中,第 段极长下降子段的长度和。先考虑长度为 的排列,有 个连续下降段的方案数怎么算。
显然有性质 ,且有递推式:
也就是每个 的位置或者序列开头放置一个 ,上升个数不变;其他情况上升个数减一。
边界是 。
有 段极长下降子段等价于有 个小于号。这种情况下重标号分配并不容易,如何求出第 段下降段的长度和?
可以考虑每个位置的贡献!枚举数字 ,它的答案是 。
这样处理欧拉数和计算答案都是 。
口胡证明是构造一个从排列到序列的映射,但是细节上出了点小问题。具体数学上好像有个代数证明。
考虑求一行欧拉数,设 表示 的排列中钦点 个间隔是小于的方案数,那么:
可以直接广义组合数分配标号搞出来,就是 。不会搞/kk
[LOJ6271] 生成树求和
考虑先拆位 ,然后对于这一位,需要计算所有“生成树边权在三进制下不进位加法和”在十进制下的和。
考虑由于是不进位加法,最后的结果一定是 中的一种。那么可以在边权上放一个 ,可以用矩阵树定理求出一个 ,我们关注的是 这个多项式。
设结果是 的分别有 棵树,假设这是第 位,那么贡献就是 。
那么只考虑计算 ,可以发现不需要真的做循环卷积,直接将 代入多项式求值即可,也就是单位根反演:
很可惜模数没有单位根,考虑 ,设 扩域即可。
注意基尔霍夫矩阵是度数矩阵减掉邻接矩阵,这里 的度数指的是所有与其相邻点的边权和!!邻接矩阵 是 的边权!!
[ARC096C] Everything on It
考虑容斥,钦点 个数字出现了不足两次,然后枚举这 个数字出现在了 个选出的子集里面,应该是:
注意有 。
考虑组合意义,前面是“先从 数中选出若干,然后分到 个集合里”。可以认为剩下的元素被分到了垃圾堆,由于不能留空集,因此再加入一个元素,答案就是 。
就是 。
[CF1295F] Good Contest
考虑将值域按照区间的起点化成若干段。比如 就把值域划分成了 这些段。
设 表示考虑前 个位置,最后一个位置落在 之后的段的方案数。考虑枚举 这一个数字段影响的范围是 ,那么转移是 ,还需要满足 的所有数字均可以选在第 段内。
转移系数是 的形式,有 ,枚举 之后直接预处理即可。
.
训练 34 C
考虑一个简单问题,给定一个序列 和阈值 ,要求 的任意一个子段和都不大于 。可以花费 的代价将任意一个位置减一,问最小花费多少代价。
考虑对每个右端点 ,考虑一个使得 最大的 ,只需要在 这个位置把 超过 的部分减掉就好了。感性理解一下这样贪很对,然后就有了线性做法。
往上套区间查询??
训练 34 B
考虑先建出广义 SAM,然后拿 SAM 读入一个串 ,从经过的每个点向上跳 nxt 链,经过的总点数和是均摊 。
也就是说,我从每个读入位置向上暴力跳链,跳过的位置不跳,这样的复杂度是 。然后 BIT 大力就是 50,改成差分草了 80.../jk
考虑每次向上覆盖颜色,实际上是等价于一个 access,然后就是树点涂色这个题,直接大力树剖覆盖难写的跟 shi 一样。
考虑拿 LCT 维护这个东西,实链存一些点到根之间的同色段,然后一次修改就直接大力 access 改上去,拿个数组差分解决这个东西,就是 。
训练 34 A
考虑如果斜率都知道,那么这题就做完了,然后爆搜就是 50pts.
考虑如果按照 排序的话,那么最中间的向量一定是最靠前的那个,之后的每一个一定在凸包的左边或者右边扩展。然后就可以 DP?
一个向量有两个可能的 ,用最大的那个排序就好了,之后直接 DP 也是对的/fad 玄学...
[CF618E] Robot Arm
线段树维护一些向量形如 ,然后对于最底层节点单独维护一个辐角形式 ,然后单点改就直接下放到最底层改 ,然后更新 ,然后合并上去,就没了。
错了。一次修改不止改了一个向量,应该是一个后缀的所有向量做旋转笛卡尔系。
每次以 个向量的起始为坐标原点,在笛卡尔系内旋转向量。
把一个系内的向量,以坐标系为参考,逆时针旋转 角的旋转矩阵是:
也就是 ,推导可以直接考虑辐角形式,然后恒等变换搞出来。
所以直接后缀打矩阵 tag 即可。
[AGC034F] RNG and XOR
min-max 容斥。
考虑先算选出 的期望次数是 ,那就枚举一个 的子集 ,需要计算 ,有:
那么子集反演:
以上均可以高维前缀和处理,复杂度 。
错了!这么算不对,再 min-max 容斥一遍也不对!列方程是啥东西啊/kk
.
35 A
考虑把一个串合并到它的最小循环节上面去,然后剩下的串数实际上就是答案。也就是说,要求最小循环节等于本串的数的个数。
考虑长度为 的串一共有 个。考虑减掉不合法的,就是枚举一个 ,减掉长度为 的最小循环节等于本串的数的个数,因为这些串中的每一个的若干次方都可以唯一对应一个不合法串。
还有一个小问题,就是长度为 的串之间的同构。容易发现这样减掉之后,每个本质不同串的每个轮换都被计算了一次贡献,所以再除掉 即可。
也就是有转移 ,答案是 。
狄利克雷卷积暴力计算的复杂度就是 ,这已经很快了,大多数情况下是足够用的。但是在某些特殊情况下,这还可以进一步优化,这种情况是计算狄利克雷前缀和:
设 ,那么 能贡献到 当且仅当 。
然后发现这类似于高维前缀和,可以用每个素数单独转移,复杂度就是 。
先枚举维数,然后从小到大枚举数字,做前缀和。注意必须从小到大,实际上就是这个维度上的前缀和。
整理一下三种变式:
xxxxxxxxxx21for(int i = 1; i <= p[0]; i++) for(int j = 1; j * p[i] <= n; j++) 2 f[j * p[i]] = add(f[j * p[i]], f[j]);xxxxxxxxxx21for(int i = 1; i <= p[0]; i++) for(int j = n / p[i]; j; j--) 2 f[j * p[i]] = add(f[j * p[i]], P - f[j]);这个好像不能做/kk
后面两个是半在线的,注意区分一下。
35 C
冷静一下,发现需要求 的每个前缀的本质不同子串数, 的每个前缀 和 的共同本质不同子串数, 的每个前缀的本质不同回文串数。
第一个和第三个都很好求,考虑第二个。
第一种思路是对 和 建立广义 SAM,这个显然不能做到低于 。
第二种思路是拿 的 SAM 读入 ,然后考虑每个位置的贡献。这个位置的贡献需要维护 的 SAM,同时维护当前的读入长度 和在 SAM 上的位置 ,那么贡献就是 。但是显然也没法做到低于 。
[CF1264D2] Beautiful Bracket Sequence
考虑给定一个括号串,如何求它的深度。实际上就是找一个间隔,使得间隔左边的左括号个数等于间隔右边的右括号个数,然后答案就是间隔左边的左括号个数。
那么直接 DP,设 表示前缀 有 个左括号的方案数,转移是直接的。枚举一个 ,把 算到答案里面即可。复杂度 。
考虑实际上不需要 DP,枚举一个位置 ,前后的转移系数是:
是 前面的左括号个数, 是 后面的右括号个数, 是 前面的问号个数, 是 后面的问号个数。
换下枚举指标,实际上等于:
.
幂级数的收敛域和收敛半径:zhihu
等比级数的闭合形式,亦即数列 的 OGF 对矩阵存在解析意义,也就是:
这个还是记住吧...
求逆方式 zhihu:将单位矩阵拼在 后面得到增广矩阵,然后通过行列变换将前一个矩阵消成单位矩阵,此时后面的矩阵就是 的逆矩阵。
的逆矩阵等于 伴随矩阵除以 的行列式。所以我们有了一个计算伴随矩阵和代数余子式的 做法:先求 的逆,然后乘上 的行列式。
行列式为零等价于有一行没有主元,这个可以直接在高斯消元的时候判断。
36 B
考虑递推式:
边界是 ,求 的值。 可能为 ,此时需要判断幂级数是否收敛,并求它的收敛域。
显然矩阵求逆随便解决任何 的情况,但是注意矩阵不存在逆的时候,我们并没有办法判断这个东西是否收敛,以及求它的收敛域,这是矩阵逆的性质导致的。
考虑这种二元递推关系,把它表示称复数的形式 ,那么 ,有 。
好了因为这个优雅的性质,这题可以用另一种途径做,因为 ,这是一个类似于多元生成函数的性质,我们尝试代入求和然后直接求出一个闭合形式。
这是有限情况,其中 ,这样有限随便做。
无限情况下,有闭合形式 ,这个形式的收敛半径是
级数收敛等价于上式虚部收敛,等价于 。
如果 那么直接输出 error,否则直接套柿子求逆。注意复数求逆也满足费马小定理之类的规律,也就是可以直接拿共轭那一套来求逆,也可以直接快速幂。
另一个点,求 ,可以奇偶分类:
xxxxxxxxxx51Func Sum(a, n){ // sum_{i=0...n-1} a^i2 if(!n) return 13 if(!(n & 1)) return (1 + a) * Sum(a, n >> 1)4 return 1 + (a + a * a) * Sum(a, n >> 1)5}复杂度严格单 。
36 C
考虑给出一条链,求有多少个三元组 满足 且 。
考虑分块,每 个元素分成一块,枚举中间元素 ,然后分类讨论:
因此这样复杂度就是 , 取 时取得最优复杂度 。
依然很卡常。
.
一个串的本质不同子串数量就是 级别的,没有更低的上界!
但是暴力跳 链可能会有一些奇怪的复杂度...
NOI OL A
数列:0110100110010110...
的分布规律是,popcount(x)%2=1 的位置是某种数字,popcount(x)%2=0 的位置是另一种数字。
还需要一个结论:
考虑一个 60pts 的做法,设 表示前 个数字,0/1 的部分,的 次方之和,考虑转移:
这样可以 的求出所有 的值。
考虑设 表示将 整体右移 个位置,有:
注意这里 0/1 恰好反转。然后就能拼出答案了。
预处理 ,那么 。
的 可以 预处理出来,空间 。
对于 的 ,直接插出来是 ,然后复杂度爆炸。预处理是 ,大概有 80pts。不会/kk
.
NOIO / NOIP 答辩题选做
[NOIO2021TG A] 愤怒的小 N
设 ,答案是 。这样拆分:
前面是自然数幂和问题:
预处理 的下降幂,直接计算就是 。
考虑后面,考虑一个分治的过程,即从高向低枚举 的每个有效位 ,需要算出从上一次的边界开始的 个位置的答案,然后向右边递归分治(实际上循环模拟即可)。
设 表示前 个位置,所有 0/1 的位置的 次方之和,有转移:
设考虑左边界是 ,算 个位置的 式,并且前面算过的段数的奇偶性是 的答案是 ,那么有:
注意到当 的时候,对所有 满足 ,也就是 ,这时候直接 return 0 即可。
这样实际上只有 个位置算了贡献,总复杂度就是 。
[NOIO2021TG C] 岛屿探险
考虑如果 ,那么相当于查询区间内满足 的 有多少,这是经典问题。
建立可持久化 Trie,然后讨论:
考虑如果 怎么做?考虑对于一个 能贡献给哪些 。
可以枚举使得 的第一个位置,就可以确定 的一些高位数字,放在 Trie 树上就对应一个节点。考虑拿 Tire 插入二元组 ,然后讨论:
答案就是插入 的过程中走过的那条链的和。实际上这个做法是对 建立了 Trie 然后考虑贡献。
这样就有大概 55pts.
考虑线段树分治,先离线询问挂在线段树上,那么每个节点都是整段询问,把它们按照 排序。
然后对每个节点单独做,就是直接把 按照 排序。这样线段树一共有 层,每层有 个元素,建立 Trie 和查询均需要 的时间,因此复杂度是 。
.
[HDU5390] Tree
总结下拿线段树分治解决可持久化问题的思路。
这道题首先树剖,然后询问拆成 个。然后对于线段树上每个节点,直接维护它的操作序列,那么一次修改,会修改线段树上 层每层的一个节点,也就是在操作序列里面加一项“删除...插入...”。
最后拿一棵 Trie 去遍历线段树结构,不需要可持久化,直接枚举询问,顺序执行操作序列,复杂度 ,空间 。
树剖常数小,大力松一松。
[LG P5807] Which Dreamed It
有向图的欧拉回路存在当且仅当每个点的入度等于出度。几个计数定理:
这里欧拉图指存在欧拉回路的图,默认不允许存在空点。两条欧拉回路不同,当且仅当经过边形成的环旋转不同构。
无向图的基尔霍夫矩阵是“每个点相邻边的权值和矩阵”减去边权邻接矩阵。
有向图类似:如果要算外向树,那么相邻边只统计入边;否则只统计出边。
模数是质数!!1
[JSOI2018] 战争
首先存在两个三角形有交就一定存在两个四边形有交....然后等价于两个凸包有交。
考虑求凸包 位移 和凸包 有交,当且仅当 。
那么可行的 应当落在一个凸包 内,满足 的点集是 的闵可夫斯基和。
上面这个结论指出了这个凸包上的点数是 的。
考虑如何求出这个凸包,可以发现这个凸包就是把两个凸包上的边极角排序之后顺次连接起来。先把两个凸包都用向量表示,然后把向量合起来极角排序,定义象限序为“四 < 一 < 二 < 三”然后排序,然后维护出和凸包横坐标最小的那个点,然后按照排序后的向量转一圈就求出来了。
考虑如何判断一个点在凸包内部,找到这个点横坐标处的上凸壳上的边和下凸壳上的边即可,然后叉积随便搞搞。
写麻烦了......
定义点的大小是先按 升序再按 降序,凸包从下凸壳转到上凸壳。
求闵可夫斯基和可以直接维护出两个凸包的一圈点,然后归并。凸包上的第一个点一定是 ,接下来的点比较那条向量比较靠下即可。
判断一个点在凸包内部,可以把凸包上所有点和查询点都减去第一个点的坐标。这样凸包上所有点在一个半平面内且按照极角序排列,可以直接二分找到现在查询点在哪一个三角剖分里面,就可以判断了。
注意可以在凸包后面多留一个点(也就是第一个点)来避免 border case。
xxxxxxxxxx401class POI{2 public: LL x, y;3 POI operator + (const POI &o) const {POI r; r.x = x + o.x, r.y = y + o.y; return r;}4 POI operator - (const POI &o) const {POI r; r.x = x - o.x, r.y = y - o.y; return r;}5 LL operator * (const POI &o) const {return x * o.x + y * o.y;}6 LL operator ^ (const POI &o) const {return x * o.y - y * o.x;}7 bool operator < (const POI &o) const {return x ^ o.x ? x < o.x : y > o.y;}8} a[CN], b[CN], c[CN], stk[CN]; int n, m, q, top;9POI P(int x, int y) {POI r; r.x = x, r.y = y; return r;}10bool cmp(POI a, POI b) {return (a ^ b) > 0;}11void bd(POI a[], int &n){12 sort(a + 1, a + n + 1), top = 0;13 for(int i = 1; i <= n; i++){14 while(top > 1 && ((stk[top] - stk[top - 1]) ^ (a[i] - stk[top])) < 0) top--;15 stk[++top] = a[i];16 }17 for(int i = n - 1; i; i--){18 while(top > 1 && ((stk[top] - stk[top - 1]) ^ (a[i] - stk[top])) < 0) top--;19 stk[++top] = a[i];20 }21 n = top - 1; for(int i = 1; i <= n; i++) a[i] = stk[i];22}23void work(){24 c[top = 1] = a[1] + b[1], a[n + 1] = a[1], b[m + 1] = b[1]; 25 int i = 1, j = 1;26 while(i <= n || j <= m){27 if(j > m || (i <= n && ((a[i + 1] - a[i]) ^ (b[j + 1] - b[j])) > 0))28 c[top + 1] = c[top] + (a[i + 1] - a[i]), i++, top++;29 else c[top + 1] = c[top] + (b[j + 1] - b[j]), j++, top++;30 }31 n = top - 1;32 for(int i = 1; i <= top; i++) a[i] = c[i] - c[1];33 // for(int i = 1; i <= top; i++) printf("%d %d\n", c[i].x, c[i].y);34}35void qu(int x, int y){36 POI cur = P(x, y) - c[1]; int pos = upper_bound(a + 2, a + n + 1, cur, cmp) - a - 1;37 if(pos < 1 || pos > n) return 0;38 if(((cur - a[pos]) ^ (a[pos + 1] - a[pos])) <= 0) return 1;39 return 0;40}.
争取字数破 3w!
38 B
现在有 个 01 方程,有 个变量,每个方程只有 个位置是有效的,现在要判断方程是否有解。
考虑维护最简形式的线性基,然后加入一个向量。因为我们只需要对每个 1 所在的位置,确定这个位置是否有主元;如果没有,则确定主元并向上下消元。
确定主元一共只会进行 次,每次消元的复杂度是 ,这部分的复杂度是 。对每个向量,用 bitset::_Find_first() 和 bitset::_Find_next() 遍历所有 1 的位置是 的,因此总复杂度是 的。
考虑判断有解,当且仅当所有未插入线性基的方程消元结束之后等号右边是零。
这一类复杂度可以这样分析:一共有 个 1,对每个 1 消去它需要 的复杂度;一共会进行 次消元,每次的复杂度是 。
这样在线维护比离线高斯消元的复杂度要优。
结论:任意一个八个方块的子矩形,如果都满足外围格子去掉四个角的异或和是零,那么就可以。
38 A
首先我们求所有情况下,从 到所有点的最短路之和,最后结果除 就是答案。
考虑对着最短路树 DP,设 表示 个点的图,考虑到了最短路树的第 层,第 层有 个点的方案数; 表示边权和。转移:
标号重分配!!!
考虑压状态,选择一个出现次数比较小,影响比较小的变量,然后对这个变量的所有求和一起 DP。这里选择 。
后面不好处理,再添加状态,设 ,后面的转移是:
的转移是:
边界是 ,答案是 。
[LG P4195] 扩展BSGS
之前想到过为什么适用性会和模数的素性有关,当时感性的给出了一个解释,现在看来好像犯了一个傻.....
重新理性推导一遍。BSGS 基于这个等价转换:
证明是显然的,只需要满足 的取值上界 可以使得 。
等价于:
既然是必要的那么只需要检验一下就可以解决充分性问题。还有一个问题是模数的倍数无法区分,这个只需要把数字改写成 的形式即可。
玄学,弃了。
.